from absl import app, flags
import datetime
import pandas as pd

from fpdf import FPDF
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import matplotlib.ticker as mtick
from google.protobuf.json_format import MessageToDict

from coin.support.proto_log.logic.util import run_from_strat_log_archive
from coin.proto.coin_strategy_pb2 import StrategyLog
from coin.proto.coin_order_gateway_pb2 import OrderGatewayLog, OrderEvent

FLAGS = flags.FLAGS


class ExecReportGenerator(object):
  def __init__(self):
    self.events = []
    self.submitted_orders = []
    self.filled_orders = []

  def handle(self, timestamp, log):
    pb = StrategyLog()
    pb.ParseFromString(log)
    if pb.type == StrategyLog.OG_LOG:
      og_log = pb.og_log
      if og_log.type == OrderGatewayLog.ORDER_EVENT:
        event = og_log.event
        self._handle_order_event(event)
        self.events.append(event)

  def _handle_order_event(self, order_event):
    if order_event.type == OrderEvent.ORDER_ACCEPTED:
      self.submitted_orders.append(order_event)
    elif order_event.type == OrderEvent.ORDER_FILLED:
      self.filled_orders.append(order_event)

  def _agg_report(self, order_df, label, order_nums):
    first_fill = datetime.datetime.utcfromtimestamp(int(int(order_df['eventTime'].iloc[0]) / 1e+9))
    last_fill = datetime.datetime.utcfromtimestamp(int(int(order_df['eventTime'].iloc[-1]) / 1e+9))
    qty = order_df['fillQty'].sum()
    turonver = (order_df['fillPrice'] * order_df['fillQty']).sum()
    vwap = turonver / order_df['fillQty'].sum()
    fill_ratio = len(order_df['externalOrderId'].drop_duplicates()) / order_nums
    return pd.DataFrame(
        [[label[0], label[1], first_fill, last_fill, qty, turonver, vwap, fill_ratio]],
        columns=[
            'trading_date',
            'Time',
            'first_fill',
            'last_fill',
            'qty',
            'turnover',
            'vwap',
            'fill_ratio'
        ])

  def _handle_orders(self, orders):
    df_list = []
    for order in orders:
      df_list.append(MessageToDict(order))
    order_df = pd.DataFrame(df_list)
    order_df.sort_values('eventTime', inplace=True)
    order_df['hour'] = order_df['eventTime'].apply(
        lambda x: datetime.datetime.utcfromtimestamp(int(x) / 1e+9).hour)
    order_df['hour'] = order_df['hour'].apply(lambda x: "%s:00" % x)
    order_df.set_index(['tradingDate', 'hour'], inplace=True)
    return order_df

  def generate_report(self):
    filled_df = self._handle_orders(self.filled_orders)
    submited_df = self._handle_orders(self.submitted_orders)
    total_qty = filled_df['fillQty'].sum()
    report = pd.DataFrame()
    for index in filled_df.index.drop_duplicates():
      order_nums = len(submited_df.loc[index, 'externalOrderId'].drop_duplicates())
      report = report.append(
          self._agg_report(filled_df.loc[index], label=index, order_nums=order_nums))
    report = report.append(
        self._agg_report(filled_df,
                         label=('TOTAL', ''),
                         order_nums=len(submited_df['externalOrderId'].drop_duplicates())))
    report['qty'] = report['qty'].apply(lambda x: round(x, 3))
    report['turnover'] = report['turnover'].apply(lambda x: round(x, 3))
    report['vwap'] = report['vwap'].apply(lambda x: round(x, 5))
    report['exec_pct'] = report['qty'] / total_qty
    report['accm_exec_pct'] = report['exec_pct'].cumsum()
    report['fill_ratio'] = report['fill_ratio'].apply(lambda x: ''
                                                      if pd.isnull(x) else '{:.2%}'.format(x))
    report['exec_pct'] = report['exec_pct'].apply(lambda x: ''
                                                  if pd.isnull(x) else '{:.2%}'.format(x))
    report['accm_exec_pct'] = report['accm_exec_pct'].apply(lambda x: ''
                                                            if pd.isnull(x) else '{:.2%}'.format(x))
    report['accm_exec_pct'].iloc[-1] = ''
    report.set_index(['trading_date', 'Time'], inplace=True)
    return report


def plot_exec_summary(df, plot_file=None):
  df = df[:-1]
  df['datetime'] = df['trading_date'].astype(str) + ' ' + df['Time']
  df['datetime'] = df['datetime'].apply(lambda x: datetime.datetime.strptime(x, "%Y%m%d %H:%M"))
  fig, ax = plt.subplots(2, 1, sharex=True, figsize=(10, 8), gridspec_kw=dict(height_ratios=[1, 1]))
  ax1, ax2 = ax
  p1 = ax1.plot(df['datetime'].values, df['vwap'].values, label='vwap', color='red')
  ax1.set_ylabel('vwap')
  ax1_t = ax1.twinx()
  p2 = ax1_t.plot(df['datetime'].values,
                  [float(x.replace('%', '')) for x in df['accm_exec_pct'].values],
                  label='accm_exec_pct',
                  color='green')
  ax1_t.yaxis.set_major_formatter(mtick.PercentFormatter())
  ax1_t.set_ylabel('accm_exec_pct')
  lines = [p1[0], p2[0]]
  ax1.legend(lines, [l.get_label() for l in lines], loc="upper left")
  ax1.set_xlim((df['datetime'].iloc[0], df['datetime'].iloc[-1]))
  ax1.xaxis.set_major_locator(mdates.HourLocator(interval=6))
  ax1.xaxis.set_major_formatter(mdates.DateFormatter('%m-%d %HH'))
  ax1.grid(True)
  p3 = ax2.bar(df['datetime'].values,
               df['qty'].values,
               align='center',
               width=0.01,
               label='volume',
               color='blue')
  ax2.legend([p3[0]], ['volume'], loc='upper left')
  ax2.set_ylabel('volume')
  ax2.set_xlim((df['datetime'].iloc[0], df['datetime'].iloc[-1]))
  yticks = ax1.yaxis.get_major_ticks()
  yticks[-1].label1.set_visible(False)
  plt.subplots_adjust(hspace=.0)
  plot_file = plot_file or 'exec_summary.png'
  fig.savefig(plot_file)
  return plot_file


def conver_to_pdf_report(df, pdf_file=None):
  df = df.reset_index()
  pdf = FPDF()
  pdf.add_page()
  pdf.set_xy(0, 0)
  pdf.set_font('arial', 'B', 12)
  pdf.cell(60)
  pdf.cell(75, 10, "Execution Service Report", 0, 2, 'C')
  pdf.cell(90, 10, " ", 0, 2, 'C')
  pdf.set_font('Times', 'B', 9)
  pdf.cell(-40, ln=1)
  for col in df.columns:
    pdf.cell(len(col) + 10, 10, col, 1, 0, 'C')
  pdf.ln()
  pdf.set_font('Times', '', 7)
  for i in range(len(df)):
    for col in df.columns:
      value = str(df.ix[i, col])
      if ' ' not in value:
        pdf.cell(len(col) + 10, 10, str(df.ix[i, col]), 1, 0, 'C')
      else:
        y_axis = pdf.y
        x_axis = pdf.x + len(col) + 10
        pdf.multi_cell(len(col) + 10, 5, str(df.ix[i, col]), 1, 'C')
        pdf.y = y_axis
        pdf.x = x_axis
    pdf.ln()
  pdf.cell(90, 10, " ", 0, 2, 'C')
  pdf.ln()
  plot_file = plot_exec_summary(df)
  pdf.image(plot_file, w=200)
  pdf_file = pdf_file or 'Execution Service Report.pdf'
  pdf.output(pdf_file, 'F')


def main(_):
  hander = ExecReportGenerator()
  start_time = datetime.datetime.strptime(FLAGS.start_time, "%Y-%m-%d %H:%M:%S")
  end_time = datetime.datetime.strptime(FLAGS.end_time, "%Y-%m-%d %H:%M:%S")
  run_from_strat_log_archive(on_log_callback=hander.handle,
                             start_time=start_time,
                             end_time=end_time,
                             root_dir=FLAGS.root_dir,
                             machine=FLAGS.machine,
                             strategy_name=FLAGS.strategy_name)

  result_summary = hander.generate_report()
  pd.set_option('display.max_colwidth', -1)
  print(result_summary.to_string())
  conver_to_pdf_report(result_summary)


if __name__ == '__main__':
  flags.DEFINE_string('start_time', None, '%Y-%m-%d %H:%M:%S')
  flags.DEFINE_string('end_time', None, '%Y-%m-%d %H:%M:%S')
  flags.DEFINE_string('root_dir', None, 'root_dir')
  flags.DEFINE_string('machine', None, 'machine')
  flags.DEFINE_string('strategy_name', None, 'strategy_name')
  app.run(main)
